#include "material.h"
#include "shader.h"
#include <stdlib.h>

#ifndef GL_VERSION_2_0
    #warning Opengl 2.0 not declared
#endif
#if defined GL_VERSION_2_0 || defined GL_ARB_shader_objects
    #ifdef __linux
        #define GL_SHADER_SUPPORT
    #endif
    #if (defined GL_ARB_geometry_shader4 || defined GL_EXT_geometry_shader4 || defined GL_NV_geometry_program4 || defined GL_NV_geometry_shader4) && defined GL_VERSION_3_0
//        #define GL_GEOMETRY_SHADER
    #endif
#endif

namespace sleek
{
    namespace mesh
    {
        void Print_GLSL_Error(GLuint shad)
        {
            #ifdef __linux
                GLsizei logsize = 0;
                char *log = NULL;
                glGetShaderiv(shad, GL_INFO_LOG_LENGTH, &logsize);
                log = new char[logsize];
                glGetShaderInfoLog(shad, logsize, &logsize, log);
                printf("%s\n", log);
                delete[] log;
            #endif
        }
        void Print_PROG_Error(GLuint prog)
        {
            #ifdef __linux
                char *log = NULL;
                GLsizei logsize = 0;
                glGetProgramInfoLog(prog, logsize, &logsize, log);
                log = new char[logsize];
                memset(log, '\0', logsize);
                glGetInfoLogARB(prog, logsize, &logsize, log);
                printf("%s\n", log);
                delete[] log;
            #endif
        }

        shader::shader()
        {
            #if defined GL_SHADER_SUPPORT
                if(!(prog = glCreateProgram())) printf("Can't creat shader programme\n");
                if(!(vs = glCreateShader(GL_VERTEX_SHADER))) printf("Can't create vertex shader\n");
                if(!(ps = glCreateShader(GL_FRAGMENT_SHADER))) printf("Can't create fragment shader\n");
                #if defined GL_GEOMETRY_SHADER
                    if(!(gs = glCreateShader(GL_GEOMETRY_SHADER))) printf("Can't create geometry shader\n");
                #endif
                //  default shader
                setFragmentShader("void main(void){ gl_FragColor = gl_Color; }", "main");
                setGeometryShader("#version 120\n"
                                  "void main()"
                                  "{"
                                  "  for(int i = 0; i < gl_in.length(); i++)"
                                  "  {"
                                  "    gl_Position = gl_in[i].gl_Position;"
                                  "    EmitVertex();"
                                  "  }"
                                  "  EndPrimitive();"
                                  "}" , "main");
                setVertexShader("void main(void){ gl_Position = gl_ModelViewProjectionMatrix*gl_Vertex; }", "main");
            #endif
        }
        shader::~shader()
        {
            #if defined GL_SHADER_SUPPORT
                detacheShader(shd_frag | shd_vert | shd_geom);
                glDeleteShader(gs); glDeleteShader(vs); glDeleteShader(ps);
                glDeleteProgram(prog);
            #endif
        }
        void shader::setLinkToMaterial(material* i)
        {
            link = i;
        }
        material* shader::getLinkFromMaterial()
        {
            return link;
        }
        void shader::attacheShader(core::u32 i)
        {
            #if defined GL_SHADER_SUPPORT
                i & shd_frag ? glAttachShader(prog,ps) : void();
                i & shd_vert ? glAttachShader(prog,vs) : void();
                i & shd_geom ? glAttachShader(prog,gs) : void();
            #endif
        }
        void shader::detacheShader(core::u32 i)
        {
            #if defined GL_SHADER_SUPPORT
                i & shd_frag ? glDetachShader(prog,ps) : void();
                i & shd_vert ? glDetachShader(prog,vs) : void();
                i & shd_geom ? glDetachShader(prog,gs) : void();
            #endif
        }
        void* shader::getVariable(core::os::stringc name)
        {
        }
        GLint shader::getVariableID(core::os::stringc name)
        {
            #if defined GL_SHADER_SUPPORT
                return glGetUniformLocation(prog, name.c_str());
            #else
                return -1;
            #endif
        }
        void shader::setVariable(core::os::stringc name, core::f32 val)
        {
            #if defined GL_SHADER_SUPPORT
                int id = glGetUniformLocation(prog, name.c_str());
                begin(); glUniform1f(id, val);
            #endif
        }
        void shader::setTexture(core::os::stringc name, GLuint val, core::u32 textureUnit)
        {
            #if defined GL_SHADER_SUPPORT
                glActiveTextureARB(GL_TEXTURE0_ARB + textureUnit);
                int id = glGetUniformLocation(prog, name.c_str());
                glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
                glBindTexture(GL_TEXTURE_2D, val);
                begin(); glUniform1i(id, textureUnit);
            #endif
        }
        void shader::setTexture(core::os::stringc name, texture::texture *val, core::u32 textureUnit)
        {
            setTexture(name, val->glTexture(), textureUnit);
        }
        void shader::setVariable(core::os::stringc name, core::math::vector3df val)
        {
            #if defined GL_SHADER_SUPPORT
                int id = glGetUniformLocation(prog, name.c_str());
                begin(); glUniform3f(id, val.x, val.y, val.z);
            #endif
        }

        void shader::setFragmentShader(core::os::stringc prog, core::os::stringc main)
        {
            #if defined GL_SHADER_SUPPORT
                GLint lenght = prog.size();
                const char *tmp = prog.c_str();
                glShaderSource(ps, 1, (const GLchar**)&tmp, &lenght);
                frag = prog;
            #endif
        }
        void shader::setVertexShader(core::os::stringc prog, core::os::stringc main)
        {
            #if defined GL_SHADER_SUPPORT
                GLint lenght = prog.size();
                const char *tmp = prog.c_str();
                glShaderSource(vs, 1, (const GLchar**)&tmp, &lenght);
                vert = prog;
            #endif
        }
        void shader::setGeometryShader(core::os::stringc prog, core::os::stringc main)
        {
            #if defined GL_SHADER_SUPPORT && defined GL_GEOMETRY_SHADER
                GLint lenght = prog.size();
                const char *tmp = prog.c_str();
                glShaderSource(gs, 1, (const GLchar**)&tmp, &lenght);
                geom = prog;
            #endif
        }

        void shader::compileShader()
        {
            comp = GL_TRUE;
            #if defined GL_SHADER_SUPPORT
                glCompileShader(vs);
                glGetShaderiv(vs, GL_COMPILE_STATUS, &comp);
                if(comp != GL_TRUE) Print_GLSL_Error(vs);

                glCompileShader(ps);
                glGetShaderiv(ps, GL_COMPILE_STATUS, &comp);
                if(comp != GL_TRUE) Print_GLSL_Error(ps);

                #if defined GL_GEOMETRY_SHADER
                    glCompileShader(gs);
                    glGetShaderiv(gs, GL_COMPILE_STATUS, &comp);
                    if(comp != GL_TRUE) Print_GLSL_Error(gs);
                #endif
                LinkAndValidate();
            #endif
        }
        void shader::LinkAndValidate()
        {
            #if defined GL_SHADER_SUPPORT
                glLinkProgram(prog);
                glGetProgramiv(prog, GL_LINK_STATUS, &comp);
                if(comp != GL_TRUE) Print_PROG_Error(prog);
                else
                {
                    glValidateProgram(prog);
                    glGetProgramiv(prog, GL_VALIDATE_STATUS, &comp);
                    if(comp != GL_TRUE) Print_PROG_Error(prog);
                }
            #endif
        }

        core::os::stringc shader::getFragmentShader()
        {
            return frag;
        }
        core::os::stringc shader::getVertexShader()
        {
            return vert;
        }
        core::os::stringc shader::getGeometryShader()
        {
            return geom;
        }

        void shader::begin()
        {
            #if defined GL_SHADER_SUPPORT
                comp == GL_TRUE ? glUseProgram(prog) : void();
            #endif
        }
        void shader::end()
        {
            #if defined GL_SHADER_SUPPORT
                glUseProgram(0);
            #endif
        }
    }
}
